 /*
  * Mango CMS
  * Copyright 2008, Mango Web, and individual contributors as indicated
  * by the @authors tag. See the copyright.txt in the distribution for a
  * full listing of individual contributors.
  *
  * This is free software; you can redistribute it and/or modify it
  * under the terms of the GNU Lesser General Public License as
  * published by the Free Software Foundation; either version 2.1 of
  * the License, or (at your option) any later version.
  *
  * This software is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  * Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with this software; if not, write to the Free
  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
  */
package org.mangocms.model.user;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.Version;

import org.hibernate.validator.Length;
import org.hibernate.validator.NotNull;
import org.mangocms.utils.crypto.EncryptionMethod;
import org.mangocms.utils.crypto.StringEncrypter;
import org.mangocms.utils.generation.RandomKeyGenerator;

/**
 * User entity
 * 
 * @author <a href="tanordheim@gmail.com">Trond Arve Nordheim</a>
 * @version $Revision: 1.1 $
 */
@Entity
@Table(name = "mango_users")
public class User implements Serializable {

	private static final long serialVersionUID = 1L;
	
	@Column(name = "id")
	@Id @GeneratedValue
	private Integer id;
	
	@Column(name = "version")
	@Version
	private Integer version;
	
	@Column(name = "username")
	@Length(max = 50)
	@NotNull
	private String username;

	@Column(name = "hashed_password")
	@NotNull
	private String hashedPassword;
	
	@Column(name = "password_salt")
	@NotNull
	private String passwordSalt;
	
	@Transient
	private String password;
	
	@Transient
	private String passwordConfirmation;
	
	@OneToOne(optional = true, cascade = CascadeType.ALL) @PrimaryKeyJoinColumn
	private UserProfile profile = null;

	@OneToMany(fetch = FetchType.LAZY)
	@JoinTable(
		name = "mango_users_groups_assoc",
		joinColumns = { @JoinColumn(name = "user_id") },
		inverseJoinColumns = { @JoinColumn(name = "group_id") }
	)
	private Set<Group> groups = new HashSet<Group>();

	/**
	 * Create a new User.
	 */
	public User() {
		profile = new UserProfile(this);
	}
	
	/**
	 * Add the user to the specified group
	 * 
	 * @param group
	 */
	public void addGroup(Group group) {
		groups.add(group);
	}
	
	/**
	 * Remove the user from the specified group
	 * 
	 * @param group
	 */
	public void removeGroup(Group group) {
		groups.remove(group);
	}
	
	/**
	 * Determine if the specified cleartext password is valid when compared
	 * to the existing hashed password data on the user object.
	 * 
	 * @param cleartextPassword
	 * @return
	 */
	public boolean isValidPassword(String cleartextPassword) {
		
		// Return false if we don't have a salt or existing password set on this
		// user instance
		if (hashedPassword == null || passwordSalt == null)
			return false;
		
		String matchPassword = StringEncrypter.encryptAsBase64(cleartextPassword, passwordSalt, EncryptionMethod.SHA1);
		return hashedPassword.equals(matchPassword);
		
	}
	
	/**
	 * Set the password for the current user.
	 * This will effectively hash the password with a randomly generated salt
	 * and assign values to the {@link hashed_password} and {@link salt}
	 * attributes. The value of this field will never be persisted to the
	 * underlying data store.
	 * 
	 * @param password
	 */
	public void setPassword(String password) {
		
		if (passwordSalt == null)
			passwordSalt = RandomKeyGenerator.getEncryptionSalt();
		hashedPassword = StringEncrypter.encryptAsBase64(password, passwordSalt, EncryptionMethod.SHA1);
		
		this.password = password;
		
	}
	
	/**
	 * Get the password for the current user.
	 * This value will always be null unless a call to
	 * {@link setPassword(String)} has been made for this instance. To get the
	 * hashed password for the user, see {@link getHashedPassword} and
	 * {@link getPasswordSalt}.
	 *
	 * @return
	 */
	public String getPassword() {
		return password;
	}
	
	/**
	 * Set the password confirmation value for the current user.
	 * This value is only used for registration purposes and is never persisted
	 * to the underlying data store.
	 * 
	 * @param passwordConfirmation
	 */
	public void setPasswordConfirmation(String passwordConfirmation) {
		this.passwordConfirmation = passwordConfirmation;
	}
	
	/**
	 * Get the password confirmation value for the current user.
	 * This value is only used for registration purposes and is never persisted
	 * to the underlying data store.
	 * 
	 * @return
	 */
	public String getPasswordConfirmation() {
		return passwordConfirmation;
	}

	/**
	 * Get the id.
	 * 
	 * @return the id.
	 */
	public Integer getId() {
		return id;
	}

	/**
	 * Set the id.
	 * 
	 * @param id The id to set.
	 */
	public void setId(Integer id) {
		this.id = id;
	}

	/**
	 * Get the version.
	 * 
	 * @return the version.
	 */
	public Integer getVersion() {
		return version;
	}

	/**
	 * Set the version.
	 * 
	 * @param version The version to set.
	 */
	public void setVersion(Integer version) {
		this.version = version;
	}

	/**
	 * Get the username.
	 * 
	 * @return the username.
	 */
	public String getUsername() {
		return username;
	}

	/**
	 * Set the username.
	 * 
	 * @param username The username to set.
	 */
	public void setUsername(String username) {
		this.username = username;
	}

	/**
	 * Get the hashedPassword.
	 * 
	 * @return the hashedPassword.
	 */
	public String getHashedPassword() {
		return hashedPassword;
	}

	/**
	 * Set the hashedPassword.
	 * 
	 * @param hashedPassword The hashedPassword to set.
	 */
	public void setHashedPassword(String hashedPassword) {
		this.hashedPassword = hashedPassword;
	}

	/**
	 * Get the passwordSalt.
	 * 
	 * @return the passwordSalt.
	 */
	public String getPasswordSalt() {
		return passwordSalt;
	}

	/**
	 * Set the passwordSalt.
	 * 
	 * @param passwordSalt The passwordSalt to set.
	 */
	public void setPasswordSalt(String passwordSalt) {
		this.passwordSalt = passwordSalt;
	}

	/**
	 * Get the groups.
	 * 
	 * @return the groups.
	 */
	public Set<Group> getGroups() {
		return groups;
	}

	/**
	 * Set the groups.
	 * 
	 * @param groups The groups to set.
	 */
	public void setGroups(Set<Group> groups) {
		this.groups = groups;
	}

	/**
	 * Get the profile.
	 * 
	 * @return the profile.
	 */
	public UserProfile getProfile() {
		return profile;
	}

	/**
	 * Set the profile.
	 * 
	 * @param profile The profile to set.
	 */
	public void setProfile(UserProfile profile) {
		this.profile = profile;
	}
	
}